import 'dart:async';

import 'package:vector_math/vector_math_64.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../controller/cut_canvas_controller.dart';
import '../controller/cut_canvas_controller_delegate.dart';
import '../core/rotate_scale_gesture_recognizer.dart';
import '../utils/common_utils.dart';
import '../utils/hero_attributes.dart';
// import 'dart:ui' as ui;

import '../cut_canvas.dart';
import 'cut_canvas_gesture_detector.dart';
import 'hit_corners.dart';

/// 处理触摸事件相关和回弹动画
///
class CutCanvasInternalWidget extends StatefulWidget {
  const CutCanvasInternalWidget({
    Key key,
    @required this.child,
    @required this.heroAttributes,
    @required this.enableRotation,
    @required this.onScaleEnd,
    @required this.controller,
    @required this.scaleStateCycle,
    @required this.scaleStateController,
    @required this.basePosition,
  }) : super(key: key);

  final Widget child;
  final CutCanvasControllerBase controller;
  final CutCanvasScaleStateController scaleStateController;
  final HeroAttributes heroAttributes;
  final bool enableRotation;

  final ScaleStateCycle scaleStateCycle;
  final Alignment basePosition;

  final CanvasScaleEndCallback onScaleEnd;

  @override
  State<StatefulWidget> createState() {
    return CutCanvasInternalState();
  }
}

class CutCanvasInternalState extends State<CutCanvasInternalWidget>
    with
        TickerProviderStateMixin,
        CutCanvasControllerDelegate,
        HitCornersDetector {
  Offset _normalizedPosition;
  double _scaleBefore;
  double _rotationBefore;

  AnimationController _canvasValueAnimController;
  Animation<CutCanvasValue> _canvasValueAnimation;

  HeroAttributes get heroAttributes => widget.heroAttributes;

  @override
  void initState() {
    super.initState();
    _canvasValueAnimController = AnimationController(vsync: this)
      ..addListener(() {
        CutCanvasValue value = _canvasValueAnimation.value;
        controller.updateMultiple(
          position: value.position,
          scale: value.scale,
          scaleByRotate: value.scaleByRotate,
          rotation: value.rotation,
          aspectRatio: value.aspectRatio,
        );
      });
    _canvasValueAnimController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        onAnimationStatusCompleted();
      }
    });

    addControllerListeners();
    addAnimateOnScaleStateUpdate(animateOnScaleStateUpdate);
    controller.setAnimValueChangeListener((to) async {
      await animateTo(to);
    });
  }

  void animateOnScaleStateUpdate(double nextScale) {
    animateReset(nextScale, Offset.zero, 0.0);
  }

  @override
  void dispose() {
    _canvasValueAnimController.dispose();
    super.dispose();
  }

  Size displaySize;

  bool get enableBouncing => true;

  @override
  Widget build(BuildContext context) {
    return _buildHero(
      child: LayoutBuilder(
        builder: (context, constraints) {
          Size canvasSize = constraints.biggest;
          return StreamBuilder(
            stream: controller.stateStream,
            initialData: controller.prevValue,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                final CutCanvasValue value = snapshot.data;

                Size childSize;
                displaySize = Size.copy(canvasSize);

                final aspectRatio = value.aspectRatio;
                if (aspectRatio == null) {
                  if (controller.photoAspectRatio == null) {
                    childSize = Size.copy(displaySize);
                  }
                }
                if (childSize == null) {
                  childSize = getFitSize(
                    displaySize,
                    controller.photoAspectRatio,
                  );
                }

                controller.updateCanvasSize(displaySize, childSize);

                /// 最大最小缩放和初始化值
                scaleBoundaries = ScaleBoundaries(
                  AdaptiveScale.contained * 1.0,
                  AdaptiveScale.contained * 5.0,
                  AdaptiveScale.contained * 1.0,
                  displaySize,
                  childSize,
                );

                final Matrix4 matrix = Matrix4.identity()
                  ..translate(value.position.dx, value.position.dy)
                  ..scale(scale * (value.scaleByRotate ?? 1.0))
                  ..rotateZ(value.rotation);

                List<Widget> children = [];

                /// 手势处理和显示控件
                final Widget customChildLayout = CustomSingleChildLayout(
                  delegate: _CenterWithOriginalSizeDelegate(
                    scaleBoundaries.childSize,
                    basePosition,
                  ),
                  child: Center(child: widget.child),
                );

                children.add(
                  CutCanvasGestureDetector(
                    onScaleStart: onScaleStart,
                    onScaleUpdate: onScaleUpdate,
                    onScaleEnd: onScaleEnd,
                    hitDetector: this,
                    child: Transform(
                      child: customChildLayout,
                      transform: matrix,
                      alignment: basePosition,
                    ),
                  ),
                );

                Widget result;
                result = Stack(children: children);
                result = Container(
                  width: displaySize.width,
                  height: displaySize.height,
                  child: result,
                );
                // result = ClipRRect(
                //   borderRadius: BorderRadius.all(Radius.circular(4)),
                //   child: result,
                // );
                result = ClipRect(child: result);
                // result = Stack(children: <Widget>[result]);
                // result = Center(
                //   child: result,
                // );

                return result;
              } else {
                return Container();
              }
            },
          );
        },
      ),
    );
  }

  Offset mapPoint(Matrix4 matrix4, Offset locationOffset) {
//    Matrix4 revertMatrix = Matrix4.inverted(matrix4);
    Vector3 mapPoint =
        matrix4.transform3(Vector3(locationOffset.dx, locationOffset.dy, 0));
    return Offset(mapPoint.x, mapPoint.y);
  }

  Widget _buildHero({Widget child}) {
    assert(child != null);
//    Widget result = widget.child;
    if (heroAttributes != null) {
      return Hero(
        tag: heroAttributes.tag,
        createRectTween: heroAttributes.createRectTween,
        flightShuttleBuilder: heroAttributes.flightShuttleBuilder,
        placeholderBuilder: heroAttributes.placeholderBuilder,
        transitionOnUserGestures: heroAttributes.transitionOnUserGestures,
        child: child,
      );
    }
    return child;
  }

  Size getFitSize(Size inputSize, double aspectRatio) {
    assert(aspectRatio != null);
    assert(inputSize != null);
    if (inputSize.width / inputSize.height > aspectRatio) {
      return Size(inputSize.height * aspectRatio, inputSize.height);
    } else {
      return Size(inputSize.width, inputSize.width / aspectRatio);
    }
  }

  Size getCoverSize(Size inputSize, double aspectRatio) {
    assert(aspectRatio != null);
    assert(inputSize != null);
    if (inputSize.width / inputSize.height > aspectRatio) {
      return Size(inputSize.width, inputSize.width / aspectRatio);
    } else {
      return Size(inputSize.height * aspectRatio, inputSize.height);
    }
  }

  Offset _lastLocalFocalPoint;
  Matrix4 _lastMatrix4;
  bool isGestureDetector = false;

  void onScaleStart(RotateScaleStartDetails details) {
    if (!controller.enableGesture) {
      return;
    }
    _rotationBefore = controller.rotation;
    _scaleBefore = scale;
    _normalizedPosition = details.focalPoint - controller.position;
    _lastLocalFocalPoint = details.localFocalPoint;
    _lastMatrix4 = Matrix4.identity()
      ..translate(controller.position.dx, controller.position.dy)
      ..rotateZ(controller.rotation)
      ..scale(scale);

    _canvasValueAnimController.stop();
    isGestureDetector = true;
  }

  void onScaleUpdate(RotateScaleUpdateDetails details) {
    if (!controller.enableGesture) {
      return;
    }
    if (details.pointerCount == 1) {
      // final Offset delta = details.focalPoint - _normalizedPosition;
      // Offset newOffset;
      // if (enableBouncing) {
      //   newOffset = getTensionOffset(
      //       delta, clampPosition(position: delta * details.scale));
      // } else {
      //   newOffset = clampPositionOutSize(position: delta * details.scale);
      // }
      // updateMultiple(
      //   position: newOffset,
      //   rotationFocusPoint: details.focalPoint,
      // );

      // TODO: 单指操作
      return;
    }

    /// 缩放后的scale值
    final double newScale = _scaleBefore * details.scale;

    /// 缩放后的Offset值
    Offset focalCenterPoint = Offset(
      _lastLocalFocalPoint.dx - displaySize.width / 2,
      _lastLocalFocalPoint.dy - displaySize.height / 2,
    );
    Vector3 lastPoint = _lastMatrix4.transform3(Vector3(0, 0, 0));

    double scaleResult;
    Matrix4 deltaMatrix;
    Offset offsetResult;
    if (enableBouncing) {
      scaleResult = newScale;
      deltaMatrix = Matrix4.identity()..scale(details.scale);
    } else {
      final double maxScale = scaleBoundaries.maxScale;
      final double minScale = scaleBoundaries.minScale;
      double clampScale = newScale.clamp(minScale, maxScale);

      scaleResult = clampScale;
      deltaMatrix = Matrix4.identity()..scale(clampScale / _scaleBefore);
    }
    Vector3 lastOriginPoint = deltaMatrix.transform3(Vector3(
        lastPoint.x - focalCenterPoint.dx,
        lastPoint.y - focalCenterPoint.dy,
        0));
//    Offset newOffset = Offset(lastOriginPoint.x + focalCenterPoint.dx,
//        lastOriginPoint.y + focalCenterPoint.dy);
    Offset newOffset = Offset(lastOriginPoint.x + focalCenterPoint.dx,
            lastOriginPoint.y + focalCenterPoint.dy) +
        details.localFocalPoint -
        _lastLocalFocalPoint;
    if (enableBouncing) {
      offsetResult = newOffset;
    } else {
      offsetResult =
          clampPositionOutSize(position: newOffset, scale: scaleResult);
    }

    /// 旋转角度值
    double newRotation = _rotationBefore;
    if (widget.enableRotation) {
      // todo 如果启用旋转，newOffset可能需要加入角度参数进行计算
      newRotation = newRotation + details.rotation;
    }

    /// 更新srt
    updateMultiple(
      scale: scaleResult,
      position: offsetResult,
      rotation: newRotation,
      rotationFocusPoint: details.focalPoint,
    );
  }

  void onScaleEnd(RotateScaleEndDetails details) {
    if (!controller.enableGesture) {
      return;
    }
    isGestureDetector = false;
    if (!enableBouncing) {
      final Matrix4 matrix = Matrix4.identity()
        ..translate(controller.position.dx, controller.position.dy)
        ..scale(controller.scale * (controller.scaleByRotate ?? 1.0))
        ..rotateZ(controller.rotation);
      Offset topLeftOffset = mapPoint(
              matrix,
              Offset(-controller.childSize.width / 2,
                  -controller.childSize.height / 2)) +
          Offset(displaySize.width / 2, displaySize.height / 2);
      Offset centerOffset = mapPoint(matrix, Offset.zero) - Offset.zero;
      Offset bottomRightOffset = mapPoint(
              matrix,
              Offset(controller.childSize.width / 2,
                  controller.childSize.height / 2)) -
          Offset(displaySize.width / 2, displaySize.height / 2);
      double newDx;
      double newDy;
      if (centerOffset.dx.abs() < 6) {
        newDx = controller.position.dx - centerOffset.dx;
      } else if (topLeftOffset.dx.abs() < 6) {
        newDx = controller.position.dx - topLeftOffset.dx;
      } else if (bottomRightOffset.dx.abs() < 6) {
        newDx = controller.position.dx - bottomRightOffset.dx;
      }
      if (centerOffset.dy.abs() < 6) {
        newDy = controller.position.dy - centerOffset.dy;
      } else if (topLeftOffset.dy.abs() < 6) {
        newDy = controller.position.dy - topLeftOffset.dy;
      } else if (bottomRightOffset.dy.abs() < 6) {
        newDy = controller.position.dy - bottomRightOffset.dy;
      }
      if (newDx == null && newDy == null) {
        controller.focusUpdate();
      } else {
        animatePositionTo(Offset(
            newDx ?? controller.position.dx, newDy ?? controller.position.dy));
      }
      return;
    }

    // 计算回弹位置

    final double _scale = scale;
    final Offset _position = controller.position;
    final double maxScale = scaleBoundaries.maxScale;
    final double minScale = scaleBoundaries.minScale;
    //animate back to maxScale if gesture exceeded the maxScale specified
    if (_scale > maxScale) {
      final double scaleComebackRatio = maxScale / _scale;
      animateBoundsReset(
          maxScale,
          clampPosition(
            position: _position * scaleComebackRatio,
            scale: maxScale,
          ));
      return;
    }

    //animate back to minScale if gesture fell smaller than the minScale specified
    if (_scale < minScale) {
      final double scaleComebackRatio = minScale / _scale;
      animateBoundsReset(
          minScale,
          clampPosition(
            position: _position * scaleComebackRatio,
            scale: minScale,
          ));
      return;
    }
    // get magnitude from gesture velocity
    final double magnitude = details.velocity.pixelsPerSecond.distance;
    // animate velocity only if there is no scale change and a significant magnitude
    if (_scaleBefore / _scale == 1.0 && magnitude >= 400.0) {
      final Offset direction = details.velocity.pixelsPerSecond / magnitude;
      Offset offset = clampPosition(position: _position + direction * 100.0);
      animatePositionTo(offset);
      return;
    }

    if (enableBouncing) {
      Offset offset = clampPosition(position: _position);
      animatePositionTo(offset);
    }
  }

  void onDoubleTap() {
    nextScaleState();
  }

  Future animateTo(CutCanvasValue to) async {
    await _animate(controller.value, to);
  }

  void animateReset(double scaleTo, Offset positionTo, double rotationTo) {
    _animate(
        controller.value,
        controller.value.copyWith(
          scale: scaleTo,
          position: positionTo,
          rotation: rotationTo,
        ));
  }

  void animateBoundsReset(double scaleTo, Offset positionTo) {
    _animate(controller.value,
        controller.value.copyWith(scale: scaleTo, position: positionTo));
  }

  void animateScaleTo(double to) {
    _animate(controller.value, controller.value.copyWith(scale: to));
  }

  void animatePositionTo(Offset to) {
    _animate(controller.value, controller.value.copyWith(position: to));
  }

  void animateRotationTo(double to) {
    _animate(controller.value, controller.value.copyWith(rotation: to));
  }

  Future _animate(CutCanvasValue from, CutCanvasValue to) async {
    _canvasValueAnimation = CutCanvasValueTween(begin: from, end: to).animate(
        CurvedAnimation(
            curve: Curves.decelerate, parent: _canvasValueAnimController));
    _canvasValueAnimController..value = 0.0;
    await _canvasValueAnimController.fling(velocity: 0.6);
  }

  void onAnimationStatus(AnimationStatus status) {
    if (status == AnimationStatus.completed) {
      onAnimationStatusCompleted();
    }
  }

  /// Check if scale is equal to initial after scale animation update
  void onAnimationStatusCompleted() {
    if (scaleStateController.scaleState != CanvasScaleState.initial &&
        scale == scaleBoundaries.initialScale) {
      scaleStateController.setInvisibly(CanvasScaleState.initial);
    }
  }
}

class _CenterWithOriginalSizeDelegate extends SingleChildLayoutDelegate {
  const _CenterWithOriginalSizeDelegate(
    this.childSize,
    this.basePosition,
  );

  final Size childSize;
  final Alignment basePosition;

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    final childWidth = childSize.width;
    final childHeight = childSize.height;

    final halfWidth = (size.width - childWidth) / 2;
    final halfHeight = (size.height - childHeight) / 2;

    final double offsetX = halfWidth * (basePosition.x + 1);
    final double offsetY = halfHeight * (basePosition.y + 1);
    return Offset(offsetX, offsetY);
  }

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return BoxConstraints.tight(childSize);
  }

  @override
  bool shouldRelayout(_CenterWithOriginalSizeDelegate oldDelegate) {
    return oldDelegate != this;
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is _CenterWithOriginalSizeDelegate &&
          runtimeType == other.runtimeType &&
          childSize == other.childSize &&
          basePosition == other.basePosition;

  @override
  int get hashCode => childSize.hashCode ^ basePosition.hashCode;
}
